به نام خدا
برنامهسازی پیشرفته
دانشگاه شهید بهشتی · دانشکده مهندسی کامپیوتر
دکتر مجتبی وحیدی اصل
وراثت
فهرست مطالب
- انتزاع و کپسوله بندی
- پلی مورفیسم
- ارثبری
- انواع ارث بری
- زیرکلاس ها و ابرکلاس ها
- کلمه کلیدی SUPER
انتزاع (Abstraction)
انتزاع چیست ؟
یکی از مزایای شیئ گرایی انتزاع است، به پنهان کردن جزئیات داخلی از چشم کاربران، انتزاع (abstraction) گفته میشود.
کاربر باید تنها کارکرد و خروجی وسیله را، مطابق با نیاز های خویش بداند.
Abstract Method چیست ؟
متدی که برای همه اشیاء یک کلاس وجود دارد اما جزئیات دقیق و پیاده سازی آن، در خود کلاس غیر ممکن بوده و در هر زیرکلاس پیاده سازی میشود را متد انتزاعی میگویند.
کپسوله بندی (encapsulation)
کپسوله بندی چیست ؟
ترکیب و بسته بندی داده ها و متد ها در یک واحد به نام شیئ، اصطلاحا کپسوله بندی گفته میشود.
به عنوان مثال هر کلاس در جاوا، یک کپسوله بندی است.
تفاوت کپسوله بندی و انتزاع
یک دوربین عکاسی دیجیتال مانند تصویر زیر در نظر بگیرید :
بر روی دفترچه راهنمای این دوربین نوشته شده است :
"کاربر محترم، در هنگام استفاده از دوربین دیجیتال کافیست بر روی دکمه های zoom in و zoom out کلیک کنید و در هنگام زوم شدن دوربین حرکت لنز را حس خواهید نمود. "
حال اگر دوربین را باز کنید با مکانیزم پیچیده آن روبرو خواهید شد که برای شما قابل فهم نخواهد بود!
فشردن دکمه عکاسی و گرفتن عکس (نتیجه دلخوواه) انتزاع نامیده میشود.
همانظور که در تصویر زیر مشخص است، زمانی که ما بر روی دکمه zoom in/out کلیک میکنیم، در درون ساختار دوربین مکانیزم هایی شامل چرخ دنده ها و لنز ها، این خواسته مارا برآورده میکنند :
به ترکیب این چرخ دنده ها و لنز ها اصطلاحا کپسوله بندی گفته میشود که به عکاس امکان می دهد زومینگ را به نرمی و سادگی انجام دهد.
چند ریختی (Polymorphism)
پلی مورفیسم یا چند ریختی چیست ؟
پلی مورفیسم یعنی کاری به شکل ها یا شیوه های مختلفی انجام شود.
به عنوان مثال "صحبت کردن" در بین همه حیوانات مشترک است، اما هر حیوانی گویش و صدای خاص خودش را دارد :
در جاوا برای رسیدن به پلی مورفیسم از باز نویسی متد (overriding) استفاده میکنیم.
ارثبری (inheritance)
ارثبری چیست ؟
فرض کنید میخواهید کلاسهایی تعریف کنید تا شکل هایی مثل دایره، مثلث و مستطیل را مدلسازی کند.
این کلاسها دارای صفات مشترک زیادی هستند مانند مساحت ، محیط و ...
به همین دلیل بهترین راه برای طراحی این کلاسها و پرهیز از تکرار نویسی، استفاده از وراثت است!
مثال هایی از وراثت در شکل های زیر مشاهده میکنید :
قاعده نحوی وراثت به صورت زیر است :
class Subclass-name extends Superclass-name {
//methods and fields
}
کلمه کلیدی extends میگوید که شما کلاس جدیدی ایجاد کرده اید که از یک کلاس موجود ارث بری می کند
به کلاس موجود، ابرکلاس یا کلاس والد (پدر) گفته میشود و به کلاس جدید نیز، کلاس فرزند میگویند.
در تصاویر، پیکان همیشه از فرزند به پدر رسم میشود :
بر اساس شکل بالا ، برنامه ای بنویسید که حقوق (salary) و پاداش (bonus) یک برنامه نویس را که از کارمند ارث بری میکند، چاپ کند.
class Employee {
float salary = 40000 ;
}
class Programmer extends Employee {
int bonus = 10000 ;
public static void main(String args[]){
Programmer p = new Programmer();
System.out.println("Programmer salary is : " + p.salary);
System.out.println("Bonus of Programmer is : " + p.bonus);
}
}
انواع ارث بری
سه نوع ارث بری بر اساس کلاس والد قابل تعریف است :
-
ساده:
ارث بری چندگانه
ارث بری چندگانه چیست ؟
هرگاه کلاسی بخواهد از چند کلاس ارث بری کند، به این نوع ارث بری، ارث بری چندگانه گفته میشود.
در جاوا ارث بری چندگانه پشتیبانی نمیشود. دلیل این امر، کاهش پیچیدگی و ساده سازی زبان است!
مثالی از ارث بری چندگانه :
در مثال زیر، دو کلاس والد متدی به نام msg دارند و کامپایلر نمی داند کدام را برای فرزند ارث بری کند.
لذا در هر حالت ( حتی عدم وجود متد های هم نام در کلاسهای والد) جاوا برای ارث بری چندگانه، خطای زمان کامپایل میگیرد.
class A{
void msg(){
System.out.println("hello");
}
}
class B{
void msg(){
System.out.println("welcome");
}
}
class C extends A,B { //suppose if it were
public static void main(String args[]){
C obj = new C();
obj.msg(); //Now wich msg() method be invoked?
}
}
Up/Down Casting
هر زیر کلاس، که از ابرکلاس ارث بری میکند، از نوع ابرکلاس نیز میباشد.
به عنوان مثال دایره و مستطیل و... هرکدام یک شکل هستند، یا گربه و سگ و ... هرکدام حیوان هستند !
به همین دلیل میتوان زیرکلاس هارا از نوع ابرکلاس هم تعریف کرد و آنها را به زیرکلاس( اگر از جنس زیرکلاس باشند ) تبدیل کرد .
البته دقت کنید در متد های انتزاعی ، نوع دقیق شیئ تعیین کننده رفتار است نه نوع ارجاء آن !
class Animal{
}
class Dog extends Animal {
public static void main(String args[]){
Animal a = new dog();
Dog b = (Dog) a;
}
}
تمرین زیرکلاس و ابرکلاس
برنامه ای با مشخصات زیر بنویسید :
ابر کلاس :
1️⃣ class name :
GeometricObject
fields :
- color : String
▶ The color of the object ( default : white )
- filled : boolean
▶Indicates whether the object is filled with a color ( default : false )
- dateCreated : jaba.util.Date
▶The date when the object was created
methods :
+ GeometricObject ()
▶Creates a GeometricObject.
+ GeometricObject (color : String , filled : boolean)
▶Creates a GeometricObject with the specified color and filled values.
+ getColor () : String
▶Returns the filled property.
+ setColor (color : String) : void
▶Sets a new color.
+ isFilled (filled : boolean ) : void
▶Returns the filled property.
+ setFilled (filled : boolean) : void
▶set a new filled property.
+ getDateCreated () : java.utin.Date
▶Returns the dateCreated.
+ toString () : String
▶Returns a string representation of this object
زیر کلاس ها :
2️⃣ class name :
Circle
fields :
- radius : double
methods :
+ Circle ()
+ Circle ( radius : double)
+ Circle ( radius : double , color : String , filled : boolean )
+ getRadius () : double
+ setRadius ( radius : double ) : void
+ getArea () : double
+ getPerimeter () : double
+ getDiameter () : double
+ printCircle () : void
3️⃣ class name :
Rectangle
fields :
- width : double
- height : double
methods :
+ Rectangle ()
+ Rectangle ( width : double , height ; double )
+ Rectangle ( width : double , height ; double , color : String , filled : boolean )
+ getWidth () : double
+ setWidth ( width : double ) : void
+ getHeight () : double
+ setHeight ( height : double ) : void
+ getArea () : double
+ getPerimeter () : double
public class GeometricObject1 {
private String color = "white";
private boolean filled;
private java.util.Date dateCreated;
/** Construct a default geometric object */
public GeometricObject1() {
dateCreated = new java.util.Date();
}
/** Construct a geometric object with the specified color
* and filled value */
public GeometricObject1(String Color, boolean filled) {
dateCreated = new java.util.Date();
this.color = color;
this.filled = filled;
}
/** Return color */
public String getColor() {
return color;
}
/** Set a new color */
public void setColor(String color) {
this.color = color;
}
/** Return filled. Since filled is boolean,
its get method is named isFilled */
public boolean isFilled() {
return filled;
}
/** Set a new filled */
public void setFilled(boolean filled) {
this.filled = filled;
}
/** Get dateCreated */
public java.util.Date getDateCreated() {
return dateCreated;
}
/** Return a string representation of this object */
public String toString() {
return "created on " + dateCreated + "\ncolor: " +
color + " and filled: " + filled;
}
}
public class Circle4 extends GeometricObject1 {
private double radius;
public Circle4() {
}
public Circle4(double radius) {
super();
this.radius = radius;
}
public Circle4(double radius, String color, boolean filled) {
super(color, filled);
this.radius = radius;
//setColor(color);
//setFilled(filled);
}
/** Return radius */
public double getRadius() {
return radius;
}
/** Set a new radius */
public void setRadius(double radius) {
this.radius = radius;
}
/** Return area */
public double getArea() {
return radius * radius * Math.PI;
}
/** Return diameter */
public double getDiameter() {
return 2 * radius;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * radius * Math.PI;
}
/* Print the circle info */
public void printCircle() {
System.out.println(toString() + "The circle is
created " + getDateCreated() +
" and the radius is " + radius);
}
public String toString() {
return "Circle WWWW " + getColor() +
super.toString();
}
}
public class Rectangle1 extends GeometricObject1 {
private double width;
private double height;
public Rectangle1() {
}
public Rectangle1(double width, double height) {
this.width = width;
this.height = height;
}
public Rectangle1(double width, double height, String color, boolean filled) {
this.width = width;
this.height = height;
setColor(color);
setFilled(filled);
}
/** Return width */
public double getWidth() {
return width;
}
/** Set a new width */
public void setWidth(double width) {
this.width = width;
}
/** Return height */
public double getHeight() {
return height;
}
/** Set a new height */
public void setHeight(double height) {
this.height = height;
}
/** Return area */
public double getArea() {
return width * height;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * (width + height);
}
}
public class TestCircleRectangle {
public static void main(String[] args) {
Circle4 circle = new Circle4(1);
System.out.println("A circle " + circle.toString());
System.out.println("The radius is " + circle.getRadius());
System.out.println("The area is " + circle.getArea());
System.out.println("The diameter is " + circle.getDiameter());
Rectangle1 rectangle = new Rectangle1(2, 4);
System.out.println("\nA rectangle " + rectangle.toString());
System.out.println("The area is " + rectangle.getArea());
System.out.println("The perimeter is " + rectangle.getPerimeter());
}
}
ارث بری از ابرکلاسها
آیا سانده ابرکلاسها ، ارث بری میشوند ؟
خیر به ارث برده نمی شوند !
قبل تر دیدیم که سازنده ها به صورت ضمنی یا صریح در بدنه کلاس قرار داده می شوند.
یک سازنده برای ایجاد یک نمونه ( یک شیئ جدید ) از یک کلاس استفاده میشود.
برخلاف فیلد های داده ای و متدها، با استفاده از کلمه کلیدی super انجام می شود.
اگر کلمه کلیدی super استفاده نشود
سازنده no-arg کلاس پدر به طور خودکار فراخوانی می شود.
درواقع یک سازنده ممکن است یک سازنده سربارگذاری شده یا سازنده ابرکلاسش را فراخوانی کند.
اگر هیج یک از این دو ، مشخصا فراخوانی نشوند، کامپایلر به طور خودکار ()super را به عنوان اولین دستور در سازنده قرار می دهد.
به عنوان مثال کد زیر :
public A(){
}
به صورت زیر کامپایل میشود :
public A(){
super();
}
همچنین کد زیر :
public A( double d ){
// some statements
}
به صورت زیر کامپایل می شود:
public A(){
super();
// some statements
}
کلمه کلیدی super :
کلمه کلیدی super در کلاس فرزند استفاده می شود و به والد آن کلاس اشاره می کند.
اگر از کلمه کلیدی super برای ارجاع به متغیر نمونه کلاس والد، وقتی هر دو یک فیلد هم نام دارند، استفاده نشود :
class Vehicle{
int speed = 50;
}
class Bike3 extends Vehicle{
int speed = 100;
}
void display(){
System.out.println(speed); // will print speed of Bike
}
public static void main(String args[]){
Bike3 b = new Bike3();
b.display();
}
class Vehicle{
Vehicle(){
System.out.println("Vehicle is created!");
}
}
class Bike5 extends Vehicle{
Bike5(){
super(); //will invoke parent class constructor
System.out.println("Bike is created");
}
}
public static void main(String args[]){
Bike5 b = new Bike5();
}
کلمه کلیدی super برای فراخوانی متد کلاس والد
همانطور که میدانیم، اگر خود ما سازنده ای قرار ندهیم، سازنده پیش فرض توسط کامپایلر ایجاد می شود.
در کلاس فرزند، کامپایلر به طور خودکار کلمه ()super را به عنوان اولین دستور به سازنده فرزند اضافه می کند.
در مواقعی که متد فرزند با متد والد همنام باشد، از کمله کلیدی super برای فراخوانی متد کلاس والد استفاده می شود:
class Person{
void message(){
System.out.println("Welcome");
}
}
class Student16 extends Person{
void message(){
System.out.println("Welcome to java");
}
}
void display(){
message() //will invoke current class message() method
super.message() //will invoke parent class message() method
}
public static void main(String args{}){
Student16 s = new Student16();
s.display();
}
زنجیره سازنده ها
ایجاد یک شیئ از یک کلاس تمامی سازنده های آن و ابرکلاسهای آن را در قالب زنجیره ای وراثتی فراخوانی می کند که به آن زنجیره سازنده ها گفته می شود.
به عنوان مثال کد زیر را در نظر بگیرید :
public class Faculty extends Employee {
public static void main(String[] args) {
new Faculty();
}
public Faculty() {
System.out.println("(4) Faculty's no-arg constructor is invoked");
}
}
class Employee extends Person {
public Employee() {
this("(2) Invoke Employee’s overloaded constructor");
System.out.println("(3) Employee's no-arg constructor is invoked");
}
public Employee(String s) {
System.out.println(s);
}
}
class Person {
public Person() {
System.out.println("(1) Person's no-arg constructor is invoked");
}
}
ردگیری اجرای کد
در هنگام اجرای کد، به ترتیب زیر اجرا می شود :
1_ Start from the main method :
public static void main(String args[])
در ابتدا خط دوم، تابع main اجرا میشود.
2_ Invoke Faculty constructor :
new Faculty();
سپس خط سوم، یعنی متد Faculty اجرا میشود که ارجاع میشود به ()public Faculty.
3_ Invoke Employee's no-arg constructor :
public Employee( );
در ادامه چون faculty از employee ارث بری میکند، سازنده employee اجرا میشود یعنی خط یازدهم.
4_ Invoke Employee(String) constructor :
this("(2) Invoke Employee's overloaded constructor");
public Employee(String s );
خود سازنده بدون آرگومان، سازنده Employee با ورودی رشته را اجرا میکند..
5_ Invoke Person() constructor :
public Person();
چون Employee از کلاس Person ارث بری میکند، در ادامه سازنده بدون آرگومان Person() را صدا میزند.
در ادامه متد های println به ترتیب زیر اجرا میشوند :
6_ System.out.println("(1) Person's no-arg constructor is invoked");
7_ System.out.println(s);
8_ System.out.println("(3) Employee's no-arg constructor is invoked");
9_ System.out.println("(4) Faculty's no-arg constructor is invoked");
بعد اجرای متد های پرینت، برنامه پایان میابد.
خطای برنامه زیر را بیابید :
public class Apple extends Fruit {
}
class Fruit {
public Fruit(String name) {
System.out.println("Fruit's constructor is invoked");
}
}
نکات اضافی
دونستن نکات زیر توی فهمیدن درس و نوشتن کد کمکتون میکنه :)
اگر کلاسی از یک کلاس انتزاعی ارث بری کند، باید تمام متد های انتزاعی را کامل پیاده سازی کند در غیر این صورت باید آن هم انتزاعی باشد.
خودمون رو بسنجیم
این بخش برای این طراحی شده که در پایان مطالعه این اسلاید، بتونی خودت رو محک بزنی و ببینی آیا مفاهیم رو به خوبی یاد گرفتی یا نه. سوالات زیر رو مرور کن و سعی کن بدون نگاه کردن به متن درس، به اون ها پاسخ بدی.
- انتزاع یا abstraction چیست و چه تفاوتی با کپسوله بندی encapsulation دارد؟
- برای وراثت یک مثال بزنید و انواع آن را نام ببرید.
- کلمه کلیدی super چه کاربرد و عملکردی دارد؟
پایان
در صورت هرگونه سوال یا پیشنهاد میتونید با من در ارتباط باشید :)
gmail: Parsahamzeie@gmail.com
telegram: @ParsaHami